#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>

#include "toc.h"

using namespace std;

#define INIT_N_HUMANS 1000
#define INIT_N_GHOULS 1

char mapData[2][80000000];

Toc::Toc(void) {}
Toc::~Toc(void) {}

void Toc::initialize(void)
{
	nMaps = 2;
	nHumans = INIT_N_HUMANS;
	mapIdx = 0;
	mapScale = 1.0f;

	mapList.push_back("map-extra.raw");
	mapList.push_back("map-extra-invert.raw");
	setupTexture();

	srandom(time(NULL));

	for (GLuint i = 0; i < INIT_N_HUMANS; i++) {
		humanList.push_back(*(new TOCHuman(i)));
	}

	for(int i = 0; i < INIT_N_GHOULS; i++) {
		ghoulList.push_back(*(new TOCGhoul()));
	}

	// cout << "init: " << humanList.size() << endl;
}

void Toc::toggleMapID(void)
{
	this->mapIdx = ++(this->mapIdx) % nMaps;
}

void Toc::loadTexture(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, mapIdx);

	glColor3f(1.0, 1.0, 1.0);

	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-mapScale, -mapScale, 0.0f);

	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-mapScale, mapScale, 0.0f);

	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(mapScale, mapScale, 0.0f);

	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(mapScale, -mapScale, 0.0f);
	glEnd();
}

void Toc::changeMapScale(GLfloat scaleFactor) {
	mapScale += scaleFactor;
}

void Toc::renderObjects(void)
{
	loadTexture();

	vector<TOCHuman>::iterator hItr;
	for (hItr = humanList.begin(); hItr != humanList.end(); hItr++) {
		hItr->drawMe();
	}

	vector<TOCGhoul>::iterator gItr;
	for(gItr = ghoulList.begin(); gItr != ghoulList.end(); gItr++) {
		gItr->drawMe();
	}
}

TOCHuman *Toc::findPotentialVictim(TOCGhoul &ghoul)
{
	GLfloat victimDistance = 2.0;
	TOCHuman *victim = NULL;
	const GLfloat victimRange = 0.025;

	for(vector<TOCHuman>::iterator i = humanList.begin();
	    i != humanList.end(); i++) {
		GLfloat d = ghoul.getDistance((TOCHuman *)&(*i));
		if (d < victimRange) {
			if(d < victimDistance) {
				victimDistance = d;
				victim = (TOCHuman *)&(*i);
			}
		}
	}

	return victim;
}

vector<TOCGhoul> Toc::updateNeighborGhouls(TOCGhoul &ghoul)
{
	vector<TOCGhoul> neighbors;
	const GLuint prox = 0.1;

	neighbors.push_back(ghoul);

	for(vector<TOCGhoul>::iterator i = ghoulList.begin(); 
	    i != ghoulList.end(); i++) {
		// if(ghoul.getDistance(&(*i)) <= prox)
		// 	ghoul.addNewGhoulNeighbor(*i);
	}

	return neighbors;
}

bool Toc::removeHuman(TOCHuman *victim)
{
	for(vector<TOCHuman>::iterator i = humanList.begin(); 
	    i != humanList.end(); i++) {
		if (victim->getID() == i->getID()) {
			humanList.erase(i);
			return true;
		}
	}

	return false;
}

void Toc::processIdle(void)
{
	vector<TOCHuman>::iterator itr;
	for (itr = humanList.begin(); itr != humanList.end(); itr++) {
		itr->computeMove();
	}

	vector<TOCHuman> recruits;
	vector<TOCGhoul>::iterator gItr;
	for(gItr = ghoulList.begin(); gItr != ghoulList.end(); gItr++) {
		TOCHuman *victim = findPotentialVictim(*gItr);
		vector<TOCGhoul> neighbors = updateNeighborGhouls(*gItr);
		victim = gItr->doGhoulStuff(neighbors, victim);

		if(victim != NULL) {
			recruits.push_back(*victim);
			removeHuman(victim);
		}

		gItr->computeMove();
	}

	for(vector<TOCHuman>::iterator ri = recruits.begin(); ri != recruits.end(); ri++) {
		ghoulList.push_back(*(new TOCGhoul(&(*ri))));
	}

}

void Toc::setupTexture(){
	FILE *im;
	GLuint mapID = 0;

	for(int i = 0; i < nMaps; i++) {
		string fName = "./images/" + mapList[i];
		im = fopen(fName.c_str(), "r");

		if(im == NULL) {
			perror(fName.c_str());
			exit(1);
		}

		fread(mapData, sizeof(char), MAP_H * MAP_W * 3, im);

		fclose(im);

		glBindTexture(GL_TEXTURE_2D, mapID++);

		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MAP_W, MAP_H, 0, GL_RGB, 
			     GL_UNSIGNED_BYTE, (const GLvoid *) mapData);


		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}
}
